﻿using CodeAngel.Domain;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace CodeAngel.UI
{
    public class AggregateDefViewModel : ViewModel
    {
        #region Constructors

        public AggregateDefViewModel(AggregateDef thisAD)
        {
            this.AggregateDef = thisAD;
        }

        #endregion

        #region AggregateDef

        /// <summary>
        /// Gets or sets the AggregateDef.
        /// </summary>
        public AggregateDef AggregateDef
        {
            get { return this._AggregateDef; }
            set
            {
                if (value != this._AggregateDef)
                {
                    this._AggregateDef = value;
                    this.InitializeAggregateDefItem();
                    this.RaisePropertyChanged("AggregateDef");
                }
            }
        }
        private AggregateDef _AggregateDef;

        #endregion

        #region AggregateDefItem

        /// <summary>
        /// Gets or sets the AggregateDefItem.
        /// </summary>
        public AggregateDefItem AggregateDefItem
        {
            get { return this._AggregateDefItem; }
            set
            {
                if (value != this._AggregateDefItem)
                {
                    this._AggregateDefItem = value;
                    this.RaisePropertyChanged("AggregateDefItem");
                }
            }
        }
        private AggregateDefItem _AggregateDefItem;

        private void InitializeAggregateDefItem()
        {
            this.AggregateDefItem = new AggregateDefItem(this.AggregateDef.EntityDef, this.AggregateDef.DataElement);
        }

        #endregion
    }

    /// <summary>
    /// Represents an item in an AggregateDef (like a TreeViewItem)
    /// </summary>
    public class AggregateDefItem : ViewModel
    {
        #region Constructors

        /// <summary>
        /// This constructor is used to create the top-level item in the hierarchy.  Any childern will be created 
        /// automatically based on its checked state.
        /// </summary>
        public AggregateDefItem(EntityDef EntityDef, DataElement rootDataElement)
        {
            this._EntityDef = EntityDef;
            this._DataElement = rootDataElement;
            this.IncludeAllScalarProperties = this._DataElement.IncludeAllScalarTypeProperties;
            this.IsUpdateable = this._DataElement.IsUpdateable;
            this.Header = this._EntityDef.Name;
            this.IsCheckedVisibility = Visibility.Hidden;
            this.IsUpdateableVisibility = Visibility.Visible;
            this.IncludeAllScalarPropertiesVisibility = Visibility.Visible;
            this.BuildChildren();
        }

        /// <summary>
        /// Private constructor used exclusively by the BuildChildern method, that creates all child items as needed.
        /// </summary>
        private AggregateDefItem(PropertyDef PropertyDef, AggregateDefItem parent)
        {
            this._PropertyDef = PropertyDef;
            this.Parent = parent;
            this.Header = this._PropertyDef.Name;
            if (this._PropertyDef.IsEntityOrCollectionType)
                this._EntityDef = this._PropertyDef.DataTypeEntityDef;
        }

        #region DataElement

        //private void InitializeDataElement(PropertyDef PropertyDef = null)
        //{
        //    if (this._AggregateDef.DataElement != null)
        //        this._DataElement = this._AggregateDef.DataElement;
        //    else
        //    {
        //        this._AggregateDef.CreateDataElement();
        //        this._DataElement = this._AggregateDef.DataElement;
        //        this._DataElement.AggregateDef = this._AggregateDef;
        //        this._DataElement.PropertyDef = PropertyDef;
        //        if (this.Parent != null)
        //            this._DataElement.ParentDataElement = this.Parent._DataElement;
        //    }
        //}

        private DataElement _DataElement;

        #endregion

        #endregion

        /// <summary>
        /// Except for the root AggregateDefItem, a AggregateDefItem always represents a PropertyDef.
        /// </summary>
        private PropertyDef _PropertyDef;

        /// <summary>
        /// If this AggregateDefItem represents a PropertyDef whose data type is an entity or collection, then
        /// this field represents the data type EntityDef for that PropertyDef.  (The only other case is
        /// if this is the root AggregateDefItem, in which case it represents the parent EntityDefintion for whom the
        /// AggregateDef is being created for.)
        /// </summary>
        private EntityDef _EntityDef;

        #region IsExpanded

        /// <summary>
        /// Gets or sets the IsExpanded.
        /// </summary>
        public bool IsExpanded
        {
            get { return this._IsExpanded; }
            set
            {
                if (value != this._IsExpanded)
                {
                    this._IsExpanded = value;
                    this.RaisePropertyChanged("IsExpanded");
                }
            }
        }
        private bool _IsExpanded = false;

        #endregion

        #region IsChecked

        /// <summary>
        /// Gets or sets the IsChecked.
        /// </summary>
        public bool IsChecked
        {
            get { return this._IsChecked; }
            set
            {
                if (value != this._IsChecked)
                {
                    this._IsChecked = value;
                    this.RaisePropertyChanged("IsChecked");

                    if (this._IsChecked)
                    {
                        this.CreateDataElement();
                        if (this._PropertyDef.IsEntityOrCollectionType)
                        {
                            this.BuildChildren();
                            this.IsExpanded = true;
                            this.IncludeAllScalarPropertiesVisibility = Visibility.Visible;
                            this.IsUpdateableVisibility = Visibility.Visible;
                        }
                    }
                    else
                    {
                        this.RemoveDataElement();
                        this.Children.Clear();
                        this.IsExpanded = false;
                        this.IncludeAllScalarPropertiesVisibility = Visibility.Collapsed;
                        this.IsUpdateableVisibility = Visibility.Collapsed;
                    }

                }
            }
        }
        private bool _IsChecked;

        /// <summary>
        /// When this AggregateDefItem is checked, there should be cooresponding data element in the aggregate.
        /// </summary>
        private void CreateDataElement()
        {
            if (this._DataElement == null)
            {
                this._DataElement = this.Parent._DataElement.DataElements.AddNew();
                this._DataElement.PropertyDef = this._PropertyDef;
            }
        }

        /// <summary>
        /// When this AggregateDefItem is not checked, there should not be cooresponding data element in the aggregate.
        /// </summary>
        private void RemoveDataElement()
        {
            this._DataElement.Delete();
            this._DataElement = null;
        }

        /// <summary>
        /// Gets or sets the IsCheckedVisibility.
        /// </summary>
        public Visibility IsCheckedVisibility
        {
            get { return this._IsCheckedVisibility; }
            set
            {
                if (value != this._IsCheckedVisibility)
                {
                    this._IsCheckedVisibility = value;
                    this.RaisePropertyChanged("IsCheckedVisibility");
                }
            }
        }
        private Visibility _IsCheckedVisibility;

        #endregion

        #region IncludeAllScalarProperties

        /// <summary>
        /// Gets or sets the IncludeAllScalarProperties.
        /// </summary>
        public bool IncludeAllScalarProperties
        {
            get
            {
                //return this._DataElement.IncludeAllScalarTypeProperties;
                if (this._DataElement != null)
                    return this._DataElement.IncludeAllScalarTypeProperties;
                else
                    return false;
            }
            set
            {
                if (value != this._DataElement.IncludeAllScalarTypeProperties)
                {
                    this._DataElement.IncludeAllScalarTypeProperties = value;
                    if (this._DataElement != null) this._DataElement.IncludeAllScalarTypeProperties = value;
                    this.RaisePropertyChanged("IncludeAllScalarProperties");
                    if (this._DataElement.IncludeAllScalarTypeProperties)
                        this.RemvoeScalarPropertiesFromChildren();
                    else
                        this.AddScalarPropertiesToChildren();
                }
            }
        }

        /// <summary>
        /// Gets or sets the IncludeAllScalarPropertiesVisibility.
        /// </summary>
        public Visibility IncludeAllScalarPropertiesVisibility
        {
            get { return this._IncludeAllScalarPropertiesVisibility; }
            set
            {
                if (value != this._IncludeAllScalarPropertiesVisibility)
                {
                    this._IncludeAllScalarPropertiesVisibility = value;
                    this.RaisePropertyChanged("IncludeAllScalarPropertiesVisibility");
                }
            }
        }
        private Visibility _IncludeAllScalarPropertiesVisibility = Visibility.Hidden;

        #endregion

        #region IsUpdateable

        /// <summary>
        /// Gets or sets the IsUpdateable.
        /// </summary>
        public bool IsUpdateable
        {
            get
            {
                if (this._DataElement != null)
                    return this._DataElement.IsUpdateable;
                else
                    return false;
            }
            set
            {
                if (value != this._DataElement.IsUpdateable)
                {
                    this._DataElement.IsUpdateable = value;
                    this.RaisePropertyChanged("IsUpdateable");
                }
            }
        }

        /// <summary>
        /// Gets or sets the IsUpdateableVisibility.
        /// </summary>
        public Visibility IsUpdateableVisibility
        {
            get { return this._IsUpdateableVisibility; }
            set
            {
                if (value != this._IsUpdateableVisibility)
                {
                    this._IsUpdateableVisibility = value;
                    this.RaisePropertyChanged("IsUpdateableVisibility");
                }
            }
        }
        private Visibility _IsUpdateableVisibility = Visibility.Hidden;

        #endregion

        #region Header

        /// <summary>
        /// Gets or sets the Header.
        /// </summary>
        public string Header
        {
            get { return this._Header; }
            set
            {
                if (value != this._Header)
                {
                    this._Header = value;
                    this.RaisePropertyChanged("Header");
                }
            }
        }
        private string _Header;

        #endregion

        #region Parent

        public AggregateDefItem Parent { get; set; }

        #endregion

        #region Children

        public ObservableCollection<AggregateDefItem> Children
        {
            get { return this._Children; }
        }
        private ObservableCollection<AggregateDefItem> _Children = new ObservableCollection<AggregateDefItem>();

        /// <summary>
        /// Builds all the children items for this item.
        /// </summary>
        private void BuildChildren()
        {
            this.Children.Clear();
            if (this._EntityDef == null) return;

            if (!this.IncludeAllScalarProperties)
                this.AddScalarPropertiesToChildren();

            foreach (var ThisPD in this._EntityDef.PropertyDefs.EntityOrCollectionProperties)
                AddAggregateDefItem(ThisPD);
        }

        /// <summary>
        /// Adds AggregateDefItem objects to the Children property, one for each scalar 
        /// property of the current EntityDef.
        /// </summary>
        private void AddScalarPropertiesToChildren()
        {
            foreach (var ThisPD in this._EntityDef.PropertyDefs.ScalarProperties)
                if (!ThisPD.IsPrimaryKey) AddAggregateDefItem(ThisPD);
        }

        /// <summary>
        /// Creates an AggregateDefItem object and adds to the Children property for the 
        /// identified property definition.
        /// </summary>
        private void AddAggregateDefItem(PropertyDef thisPD)
        {
            var ChildItem = new AggregateDefItem(thisPD, this);
            this.Children.Add(ChildItem);
            ChildItem._DataElement = this._DataElement.DataElements.Find(thisPD);
            if (ChildItem._DataElement != null)
            {
                ChildItem.IsChecked = true;
                ChildItem.IsUpdateable = ChildItem._DataElement.IsUpdateable;
                ChildItem.IncludeAllScalarProperties = ChildItem._DataElement.IncludeAllScalarTypeProperties;
            }
            //ChildItem.IsChecked = (ChildItem._DataElement != null);
            //if (ChildItem._DataElement != null) ChildItem.IncludeAllScalarProperties = ChildItem._DataElement.IncludeAllScalarTypeProperties;
        }

        /// <summary>
        /// Removes the scalar properties from the Children property.  If there are any DataElements associated
        /// with those scalar properties (ie when they are checked) the DataElements will also be removed.
        /// </summary>
        private void RemvoeScalarPropertiesFromChildren()
        {
            foreach (var ThisPD in this._EntityDef.PropertyDefs.ScalarProperties)
            {
                var ThisItem = this.Children.FirstOrDefault(i => i._PropertyDef == ThisPD);
                if (ThisItem != null)
                {
                    this.Children.Remove(ThisItem);
                    this._DataElement.DataElements.Remove(ThisItem._DataElement);
                }
            }
        }

        #endregion

    }
}
